/*
 * 文 件 名:  TokenServiceImpl
 * 版    权:
 * 描    述:  <描述>
 * 修 改 人:  redName
 * 修改时间:  2022/5/3
 * 跟踪单号:  <跟踪单号>
 * 修改单号:  <修改单号>
 * 修改内容:  <修改内容>
 */
package com.zj003.framework.service.impl;

import cn.hutool.core.util.IdUtil;
import com.zj003.common.constant.Constants;
import com.zj003.common.domain.model.LoginUser;
import com.zj003.common.util.ip.AddressUtils;
import com.zj003.common.util.ip.IpUtils;
import com.zj003.common.util.web.ServletUtils;
import com.zj003.framework.config.PropertiesConfig;
import com.zj003.framework.service.TokenService;
import eu.bitwalker.useragentutils.UserAgent;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.stereotype.Service;

import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Base64;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

/**
 * <功能详细描述>
 *
 * @author ReaName
 * @version [版本号, 2022/5/3]
 * @see [相关类/方法]
 * @since [产品/模块版本]
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class TokenServiceImpl implements TokenService, InitializingBean {

    private final PropertiesConfig propertiesConfig;

    private final RedissonClient redissonClient;

    private static final long MILLIS_SECOND = 1000;

    private static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    private static final Long MILLIS_MINUTE_TEN = 20 * 60 * 1000L;

    /**
     * 用于签名 Access Token
     */
    private Key signKey;

    /**
     * 配置签名和刷新签名
     *
     * @author RedName
     * {@code @date} 2022/5/3 3:27
     */
    @Override
    public void afterPropertiesSet() {
        signKey = new SecretKeySpec(Base64.getDecoder().decode(propertiesConfig.getJwt().getKey()), "HmacSHA512");
    }

    /**
     * 设置用户身份信息
     *
     * @param loginUser:
     * @author RedName
     * {@code @date} 2022/5/6 2:57
     */
    @Override
    public void setLoginUser(LoginUser loginUser) {
        if (ObjectUtils.isNotEmpty(loginUser) && StringUtils.isNotBlank(loginUser.getToken())) {
            refreshToken(loginUser);
        }
    }

    /**
     * 获取用户身份信息
     *
     * @param request:
     * @return java.util.Optional<com.zj003.common.domain.model.LoginUser>
     * @author RedName
     * {@code @date} 2022/5/6 3:25
     */
    @Override
    public LoginUser getLoginUser(HttpServletRequest request) {
        return getLoginUserOptional(request).<BadCredentialsException>orElseThrow(() -> {
            throw new BadCredentialsException("用户未认证");
        });
    }

    @Override
    public Optional<LoginUser> getLoginUserOptional(HttpServletRequest request) {
        // 获取请求携带的令牌
        String token = getToken(request);

        if (StringUtils.isNotEmpty(token)) {
            Claims claims = parseToken(token);
            // 解析对应的权限以及用户信息
            String uuid = claims.getSubject();
            RMapCache<String, LoginUser> mapCache = redissonClient.getMapCache(Constants.LOGIN_USER_KEY);
            LoginUser loginUser = mapCache.get(uuid);
            return Optional.ofNullable(loginUser);
        }
        return Optional.empty();
    }

    /**
     * 删除用户身份信息
     *
     * @param token:
     * @author RedName
     * {@code @date} 2022/5/6 2:58
     */
    @Override
    public void delLoginUser(String token) {
        if (StringUtils.isNotBlank(token)) {
            RMapCache<String, LoginUser> mapCache = redissonClient.getMapCache(Constants.LOGIN_USER_KEY);
            mapCache.remove(token);
        }
    }

    /**
     * 创建令牌
     *
     * @param loginUser:
     * @return java.lang.String
     * @author RedName
     * {@code @date} 2022/5/6 2:56
     */
    @Override
    public String createToken(LoginUser loginUser) {
        String uuid = IdUtil.simpleUUID();
        loginUser.setToken(uuid);

        setUserAgent(loginUser);
        refreshToken(loginUser);

        return propertiesConfig.getJwt().getPrefix().concat(buildJwtToken(uuid));

    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser:
     * @author RedName
     * {@code @date} 2022/5/6 2:55
     */
    @Override
    public void verifyToken(LoginUser loginUser) {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN) {
            refreshToken(loginUser);
        }
    }

    /**
     * 设置用户代理信息
     *
     * @param loginUser:
     * @author RedName
     * {@code @date} 2022/5/6 2:39
     */
    private void setUserAgent(LoginUser loginUser) {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr(ServletUtils.getRequest());
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIp(ip, propertiesConfig.getApp().isAddressEnabled()));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }

    /**
     * 刷新令牌
     *
     * @param loginUser:
     * @author RedName
     * {@code @date} 2022/5/6 2:45
     */
    private void refreshToken(LoginUser loginUser) {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + propertiesConfig.getJwt().getExpireTime() * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = loginUser.getToken();
        RMapCache<String, LoginUser> mapCache = redissonClient.getMapCache(Constants.LOGIN_USER_KEY);
        mapCache.put(userKey, loginUser, propertiesConfig.getJwt().getExpireTime(), TimeUnit.MINUTES);
    }

    /**
     * 创建jwt令牌
     *
     * @param uuid:
     * @return java.lang.String
     * @author RedName
     * {@code @date} 2022/5/6 2:51
     */
    private String buildJwtToken(String uuid) {
        return Jwts.builder().setId("redName").setSubject(uuid).signWith(signKey, SignatureAlgorithm.HS512).compact();
    }

    /**
     * 解析token
     *
     * @param token:
     * @return io.jsonwebtoken.Claims
     * @author RedName
     * {@code @date} 2022/5/6 3:18
     */
    private Claims parseToken(String token) {
        Jws<Claims> claimsJws = Jwts.parserBuilder().setSigningKey(signKey).build().parseClaimsJws(token);
        return claimsJws.getBody();
    }

    /**
     * 获取请求token
     *
     * @param request:
     * @return java.lang.String
     * @author RedName
     * {@code @date} 2022/5/6 3:02
     */
    private String getToken(HttpServletRequest request) {
        String token = request.getHeader(propertiesConfig.getJwt().getHeader());
        if (StringUtils.isNotEmpty(token) && token.startsWith(propertiesConfig.getJwt().getPrefix())) {
            token = token.replace(propertiesConfig.getJwt().getPrefix(), "");
        }
        return token;
    }
}
